package com.patrick.open.gateway.server.exception;

import com.patrick.open.api.core.common.RespCode;
import com.patrick.open.api.core.common.RespData;
import com.patrick.open.api.core.exception.OpenGlobalExceptionHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;

/**
 * @author patrick
 * @date 2020/6/8 11:11 下午
 * @Des 全局异常处理
 * 最簡單的事是堅持，最難的事還是堅持
 */
@Slf4j
@RequiredArgsConstructor
@Order(-2)//默认为-1
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {

  /**
   * 保存响应code
   */
  private ThreadLocal<RespData> respDataThreadLocal = new ThreadLocal<>();

  /**
   * MessageReader
   */
  private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

  /**
   * MessageWriter
   */
  private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

  /**
   * ViewResolvers
   */
  private List<ViewResolver> viewResolvers = Collections.emptyList();

  /**
   * 参考AbstractErrorWebExceptionHandler
   *
   * @param messageReaders
   */
  public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
    Assert.notNull(messageReaders, "'messageReaders' must not be null");
    this.messageReaders = messageReaders;
  }

  /**
   * 参考AbstractErrorWebExceptionHandler
   *
   * @param viewResolvers
   */
  public void setViewResolvers(List<ViewResolver> viewResolvers) {
    this.viewResolvers = viewResolvers;
  }

  /**
   * 参考AbstractErrorWebExceptionHandler
   *
   * @param messageWriters
   */
  public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
    Assert.notNull(messageWriters, "'messageWriters' must not be null");
    this.messageWriters = messageWriters;
  }

  @Override
  public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
    ServerHttpResponse response = exchange.getResponse();
    RespData respData;
    ServerHttpRequest request = exchange.getRequest();
    /**
     * 不处理ico异常
     */
    if ("/favicon.ico".equals(exchange.getRequest().getURI().getPath())) {
      return Mono.empty();//不触发 flatMap
    }
    if (ex instanceof NotFoundException) {
      respData = RespData.failed().code(RespCode.SERVICE_UNAVAILABLE.getCode()).msg(RespCode.SERVICE_UNAVAILABLE.getMsg()).httpStatus(HttpStatus.SERVICE_UNAVAILABLE.value()).path(request.getURI().getPath());
      log.error("==> 服务不可用:{}", respData);
    } else {
      respData = OpenGlobalExceptionHandler.resolveException((Exception) ex, exchange.getRequest().getURI().getPath());
    }

    //防止response缓冲区已经刷新提交
    if (response.isCommitted()) {
      return Mono.error(ex);
    }
    // header set
    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
    respDataThreadLocal.set(respData);
    //不能实现自定义code
//    return response
//      .writeWith(Mono.fromSupplier(() -> {
//        DataBufferFactory bufferFactory = response.bufferFactory();
//        try {
//          return bufferFactory.wrap(objectMapper.writeValueAsBytes(respData));
//        } catch (JsonProcessingException e) {
//          log.error("===> Error writing response", ex);
//          return bufferFactory.wrap(new byte[0]);
//        }
//      }));
    ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
    return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
      .switchIfEmpty(Mono.error(ex))
      .flatMap((handler) -> handler.handle(newRequest))
      .flatMap((res) -> {
        return write(exchange, res, ex);
      });
  }

  /**
   * 参考DefaultErrorWebExceptionHandler
   *
   * @param request
   * @return
   */
  protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    RespData result = respDataThreadLocal.get();
    return ServerResponse.status(result.getHttpStatus())
      .contentType(MediaType.APPLICATION_JSON)
      .body(BodyInserters.fromValue(result));
  }

  /**
   * 参考AbstractErrorWebExceptionHandler
   * @param exchange
   * @param response
   * @param ex
   * @return
   */
  private Mono<? extends Void> write(ServerWebExchange exchange,
                                     ServerResponse response, Throwable ex) {
    exchange.getResponse().getHeaders()
      .setContentType(response.headers().getContentType());
    log.error("==> 调用出错{},{}", exchange, ex);
    return response.writeTo(exchange, new ResponseContext());
  }

  /**
   * 参考AbstractErrorWebExceptionHandler
   */
  private class ResponseContext implements ServerResponse.Context {

    @Override
    public List<HttpMessageWriter<?>> messageWriters() {
      return GlobalExceptionConfiguration.this.messageWriters;
    }

    @Override
    public List<ViewResolver> viewResolvers() {
      return GlobalExceptionConfiguration.this.viewResolvers;
    }

  }
}
